﻿// ———————————————————————–
// <copyright file="EDXLDE.cs" company="The MITRE Corporation">
//    Copyright (c) 2010 The MITRE Corporation. All rights reserved.
//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at
//    http://www.apache.org/licenses/LICENSE-2.0
//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.
// </copyright>
// ———————————————————————–
/////////////////////////////////////////////////////////////////////////////////////////////////
// EDXLDE.cs: Top level class - Instantiate an EDXLDE object to create an EDXL-DE message
// Class To Represent an EDXL-DE Message
// Project: IC.NET - EDXL Library
//
// Language:    C#, .NET 4.0
// Platform:    Windows 7, VS 2010 Beta 2
// Author:      Don McGarry MITRE Rome

/////////////////////////////////////////////////////////////////////////////////////////////////

// Copyright (c) 2010 The MITRE Corporation. All rights reserved.
//
// NOTICE
// This software was produced for the U. S. Government
// under Contract No. FA8721-09-C-0001, and is
// subject to the Rights in Noncommercial Computer Software
// and Noncommercial Computer Software Documentation Clause
// (DFARS) 252.227-7014 (JUN 1995)

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Net.Mail;
using System.ServiceModel.Syndication;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using System.Reflection;

namespace EDXLSharp.EDXLDELib
{
  /// <summary>
  /// Class To Represent an EDXL-DE Message
  /// </summary>
  [Serializable]
  [XmlType(AnonymousType = true, Namespace = EDXLConstants.EDXLDE10Namespace)]
  [XmlRoot(ElementName = "EDXLDistribution", IsNullable = false, Namespace = EDXLConstants.EDXLDE10Namespace)]
  public partial class EDXLDE
  {
    #region Private Member Variables

    /// <summary>
    /// Stores Concatenated Schema Validation Errors
    /// </summary>
    private string schemaErrorString = String.Empty;

    /// <summary>
    /// Stores Number of Schema Validation Errors
    /// </summary>
    private int schemaErrors = 0;

    /// <summary>
    /// The unique identifier of this distribution message
    /// </summary>
    private string distributionID;

    /// <summary>
    /// The unique identifier of the sender. 
    /// </summary>
    private string senderID;

    /// <summary>
    /// The date and time the distribution message was sent. 
    /// </summary>
    private DateTime datetimeSent;

    /// <summary>
    /// The actionability of the message. 
    /// </summary>
    private StatusValue? distributionStatus;

    /// <summary>
    /// The function of the message. 
    /// </summary>
    private TypeValue? distributionType;

    /// <summary>
    /// Confidentiality of the combined distribution message’s content. 
    /// </summary>
    private string combinedConfidentiality;

    /// <summary>
    /// The primary language (but not necessarily exclusive) used in the payloads.
    /// </summary>
    private string language;

    /// <summary>
    /// The functional role of the sender, as it may determine message routing decisions. 
    /// </summary>
    private VLList senderRole;

    /// <summary>
    /// The functional role of the recipient, as it may determine message routing decisions. 
    /// </summary>
    private VLList recipientRole;

    /// <summary>
    /// The topic related to the distribution message, as it may determine message routing decisions. 
    /// </summary>
    private VLList keyword;

    /// <summary>
    /// A reference to a previous distribution message. 
    /// </summary>
    private BindingList<string> distributionReference;

    /// <summary>
    /// The identifier of an explicit recipient. 
    /// </summary>
    private List<ValueScheme> explicitAddress;

    /// <summary>
    /// The container element for location information. 
    /// </summary>
    private List<TargetAreaType> targetArea;

    /// <summary>
    /// The container element for message data and content. 
    /// </summary>
    private List<ContentObject> contentObjects;

    #endregion

    #region Constructors

    /// <summary>
    /// Initializes a new instance of the EDXLDE class
    /// Default Ctor - Initializes Lists
    /// </summary>
    public EDXLDE()
    {
      this.contentObjects = new List<ContentObject>();
      this.distributionReference = new BindingList<string>();
      this.explicitAddress = new List<ValueScheme>();
      this.keyword = new VLList("keyword");
      this.recipientRole = new VLList("recipientRole");
      this.senderRole = new VLList("senderRole");
      this.targetArea = new List<TargetAreaType>();
      this.distributionReference.ListChanged += new ListChangedEventHandler(this.DistributionRefHandler);
    }

    /// <summary>
    /// Initializes a new instance of the EDXLDE class
    /// Ctor that will parse XML from a string
    /// </summary>
    /// <param name="xmlString">String of XML Data</param>
    public EDXLDE(string xmlString)
    {
      this.contentObjects = new List<ContentObject>();
      this.distributionReference = new BindingList<string>();
      this.explicitAddress = new List<ValueScheme>();
      this.keyword = new VLList("keyword");
      this.recipientRole = new VLList("recipientRole");
      this.senderRole = new VLList("senderRole");
      this.targetArea = new List<TargetAreaType>();
      this.distributionReference.ListChanged += new ListChangedEventHandler(this.DistributionRefHandler);
      this.ReadXML(xmlString);
      this.Validate();
    }

    #endregion

    #region Public Accessors
    /// <summary>
    /// Gets or sets
    /// Uniqueness is assigned by the sender to be unique for that sender.
    /// </summary>
    [XmlElement(ElementName = "distributionID", Order = 0)]
    public string DistributionID
    {
      get { return this.distributionID; }
      set { this.distributionID = value; }
    }

    /// <summary>
    /// Gets or sets
    /// 1. Uniquely identifies human parties, systems, services, or devices that are all potential
    /// senders of the distribution message.
    /// 2. In the form actor@domain-name.
    /// 3. Uniqueness of the domain-name is guaranteed through use of the Internet Domain Name
    /// System, and uniqueness of the actor name enforced by the domain owner
    /// </summary>
    [XmlElement(ElementName = "senderID", Order = 1)]
    public string SenderID
    {
      get
      {
        return this.senderID;
      }

      set
      {
        this.CheckSenderID(value);
        this.senderID = value;
      }
    }

    /// <summary>
    /// Gets or sets
    /// 1. The Date Time combination must include the offset time for time zone.
    /// 2. Must be in the W3C format for the XML [dateTime] data type.
    /// Example: 2004-08-01T16:49:00-07:00
    /// </summary>
    [XmlIgnore]
    public DateTime DateTimeSent
    {
      get
      {
        return this.datetimeSent.ToUniversalTime();
      }

      set
      {
        if (value.Kind != DateTimeKind.Unspecified)
        {
          this.datetimeSent = value.ToUniversalTime();
        }
        else
        {
          throw new ArgumentException("TimeZone Information Must Be Specified");
        }
      }
    }

    /// <summary>
    /// Gets or sets
    /// a. Actual - "Real-world" information for action
    /// b. Exercise - Simulated information for exercise participants
    /// c. System - Messages regarding or supporting network functions
    /// d. Test - Discardable messages for technical testing only.
    /// </summary>
    [XmlElement(ElementName = "distributionStatus", Order = 3)]
    public StatusValue? DistributionStatus
    {
      get { return this.distributionStatus; }
      set { this.distributionStatus = value; }
    }

    /// <summary>
    /// Gets or sets
    /// a. Report - New information regarding an incident or activity
    /// b. Update - Updated information superseding a previous message
    /// c. Cancel - A cancellation or revocation of a previous message
    /// d. Request - A request for resources, information or action
    /// e. Response - A response to a previous request
    /// f. Dispatch - A commitment of resources or assistance
    /// g. Ack - Acknowledgment of receipt of an earlier message
    /// h. Error - Rejection of an earlier message (for technical reasons)
    /// i. SensorConfiguration - These messages are for reporting configuration during power up
    /// or after Installation or Maintenance.
    /// j. SensorControl - These are messages used to control sensors/sensor concentrator
    /// components behavior.
    /// k. SensorStatus - These are concise messages which report sensors/sensor
    /// concentrator component status or state of health.
    /// l. SensorDetection - These are high priority messages which report sensor detections
    /// </summary>
    [XmlElement(ElementName = "distributionType", Order = 4)]
    public TypeValue? DistributionType
    {
      get { return this.distributionType; }
      set { this.distributionType = value; }
    }

    /// <summary>
    /// Gets or sets
    /// The combindedConfidentiality indicates the confidentiality of the combined
    /// contentObject sub-elements. Generally the combined confidentiality is the most restrictive
    /// of the confidentiality elements in the container contentObject element, but it can be
    /// more restrictive than any of the individual confidentiality elements.
    /// </summary>
    [XmlElement(ElementName = "combinedConfidentiality", Order = 5)]
    public string CombinedConfidentiality
    {
      get { return this.combinedConfidentiality; }
      set { this.combinedConfidentiality = value; }
    }

    /// <summary>
    /// Gets or sets
    /// Valid language values are supplied in the ISO standard [RFC3066].
    /// </summary>
    [XmlElement(ElementName = "language", Order = 6)]
    public string Language
    {
      get
      {
        return this.language;
      }

      set
      {
        this.CheckRFC3066(value);
        this.language = value;
      }
    }

    /// <summary>
    /// Gets or sets
    /// The list and associated val(s) is in the form:
    /// where the content is the Name of a published list of
    /// values and definitions, and the content of val is a string denoting the val itself.
    /// </summary>
    [XmlIgnore]
    public List<ValueList> SenderRole
    {
      get { return this.senderRole; }
      set { this.senderRole.Values = value; }
    }

    /// <summary>
    /// Gets or sets
    /// The list and associated val(s) is in the form:
    /// where the content is the Name of a published list of
    /// values and definitions, and the content of val is a string denoting the val itself.
    /// </summary>
    [XmlIgnore]
    public List<ValueList> RecipientRole
    {
      get { return this.recipientRole; }
      set { this.recipientRole.Values = value; }
    }

    /// <summary>
    /// Gets or sets
    /// The list and associated val(s) is in the form:
    /// where the content is the Name of a published list of
    /// values and definitions, and the content of val is a string denoting the val itself.
    /// </summary>
    [XmlIgnore]
    public List<ValueList> Keyword
    {
      get { return this.keyword; }
      set { this.keyword.Values = value; }
    }

    /// <summary>
    /// Gets or sets
    /// The distributionID and senderID and dateTimeSent of the referenced previous
    /// message, concatenated with a comma delimiter.
    /// </summary>
    [XmlIgnore]
    public BindingList<string> DistributionReference
    {
      get { return this.distributionReference; }
      set { this.distributionReference = value; }
    }

    /// <summary>
    /// Gets or sets
    /// The list and associated val(s) is in the form:
    /// where the content is the Name of a published list of
    /// values and definitions, and the content of val is a string denoting the val itself.
    /// </summary>
    [XmlIgnore]
    public List<ValueScheme> ExplicitAddress
    {
      get { return this.explicitAddress; }
      set { this.explicitAddress = value; }
    }

    /// <summary>
    /// Gets or sets
    /// Multiple targetArea blocks may appear in a single EDXLDistribution element, in which
    /// case the target area for the current message is the union of all areas described in the
    /// various targetArea structures
    /// </summary>
    [XmlIgnore]
    public List<TargetAreaType> TargetArea
    {
      get { return this.targetArea; }
      set { this.targetArea = value; }
    }

    /// <summary>
    /// Gets or sets
    /// 1. The contentObject is the container element for specific messages.
    /// 2. The contentObject may have an optional attribute that defines a namespace prefix which
    /// resolves ambiguous element names.
    /// 3. The contentObject element MUST contain exactly one of the two content formats:
    /// a. xmlContent, for valid namespaced XML content or
    /// b. nonXMLContent, containing one or both of the elements uri, for reference to the content’s
    /// location, and contentData, for data encapsulated in the message.
    /// </summary>
    [XmlIgnore]
    public List<ContentObject> ContentObjects
    {
      get { return this.contentObjects; }
      set { this.contentObjects = value; }
    }

    /// <summary>
    /// Gets or sets
    /// XML Serialization Object for DateTimeSent
    /// </summary>
    [XmlElement(ElementName = "dateTimeSent", Order = 2)]
    public string XmlDateTime
    {
      get
      {
        return this.DateTimeSent.ToLocalTime().ToString("yyyy-MM-ddTHH:mm:sszzz");
      }

      set
      {
        try
        {
          this.DateTimeSent = DateTime.Parse(value);
        }
        catch (ArgumentNullException)
        {
          throw new ArgumentNullException("Input Value Can't Be Null!");
        }
        catch (FormatException)
        {
          throw new ArgumentException("Invalid Input String");
        }

        if (this.datetimeSent.Kind == DateTimeKind.Unspecified)
        {
          throw new ArgumentException("TimeZone Information Must Be Specified");
        }
      }
    }

    /// <summary>
    /// Gets or sets
    /// XML Serialization Object for Sender Role
    /// </summary>
    [XmlElement(ElementName = "senderRole", Order = 7)]
    public ValueList[] SenderRoleXML
    {
      get
      {
        if (this.senderRole.Count == 0)
        {
          return null;
        }
        else
        {
          return this.senderRole.ToArray();
        }
      }

      set
      {
        if (value != null)
        {
          this.senderRole.ValuesXML = value;
        }
      }
    }

    /// <summary>
    /// Gets or sets
    /// XML Serialization Object for Recipient Role
    /// </summary>
    [XmlElement(ElementName = "recipientRole", Order = 8)]
    public ValueList[] RecipientRoleXML
    {
      get
      {
        if (this.recipientRole.Count == 0)
        {
          return null;
        }
        else
        {
          return this.recipientRole.ToArray();
        }
      }

      set
      {
        if (value != null)
        {
          this.recipientRole.ValuesXML = value;
        }
      }
    }

    /// <summary>
    /// Gets or sets
    /// XML Serialization Object for Keyword
    /// </summary>
    [XmlElement(ElementName = "keyword", Order = 9)]
    public ValueList[] KeywordXML
    {
      get
      {
        if (this.keyword.Count == 0)
        {
          return null;
        }
        else
        {
          return this.keyword.ToArray();
        }
      }

      set
      {
        if (value != null)
        {
          this.keyword.ValuesXML = value;
        }
      }
    }

    /// <summary>
    /// Gets or sets
    /// XML Serialization Object for Distribution Reference
    /// </summary>
    [XmlElement(ElementName = "distributionReference", Order = 10)]
    public string[] DistributionReferenceXML
    {
      get
      {
        if (this.distributionReference.Count == 0)
        {
          return null;
        }
        else
        {
          return this.distributionReference.ToArray();
        }
      }

      set
      {
        if (value != null)
        {
          this.distributionReference = new BindingList<string>(value.ToList());
        }
      }
    }

    /// <summary>
    /// Gets or sets
    /// The list and associated val(s) is in the form:
    /// where the content is the Name of a published list of
    /// values and definitions, and the content of val is a string denoting the val itself.
    /// </summary>
    [XmlElement(ElementName = "explicitAddress", Order = 11)]
    public ValueScheme[] ExplicitAddressXML
    {
      get
      {
        if (this.explicitAddress.Count == 0)
        {
          return null;
        }
        else
        {
          return this.explicitAddress.ToArray();
        }
      }

      set
      {
        if (value != null)
        {
          this.explicitAddress = value.ToList();
        }
      }
    }

    /// <summary>
    /// Gets or sets
    /// XML Serialization Object for Target Area
    /// </summary>
    [XmlElement(ElementName = "targetArea", Order = 12)]
    public TargetAreaType[] TargetAreaXML
    {
      get
      {
        if (this.targetArea.Count == 0)
        {
          return null;
        }
        else
        {
          return this.targetArea.ToArray();
        }
      }

      set
      {
        if (value != null)
        {
          this.targetArea = value.ToList();
        }
      }
    }

    /// <summary>
    /// Gets or sets
    /// XML Serialization Object for ContentObject
    /// </summary>
    [XmlElement(ElementName = "contentObject", Order = 13)]
    public ContentObject[] ContentObjectsXML
    {
      get
      {
        if (this.contentObjects.Count == 0)
        {
          return null;
        }
        else
        {
          return this.contentObjects.ToArray();
        }
      }

      set
      {
        if (value != null)
        {
          this.contentObjects = value.ToList();
        }
      }
    }

    #endregion

    #region Public Member Functions

    /// <summary>
    /// Writes This DE Object to an XML String
    /// </summary>
    /// <returns>String of XML Data</returns>
    public string WriteToXML()
    {
      this.Validate();
      XmlWriterSettings xsettings = new XmlWriterSettings();
      xsettings.Indent = true;
      xsettings.IndentChars = "\t";
      xsettings.Encoding = Encoding.UTF8;
      StringBuilder output = new StringBuilder();
      using (XmlWriter xwriter = XmlWriter.Create(output, xsettings))
      {
        PropertyInfo innerWriterPropInfo = xwriter.GetType().GetProperty("InnerWriter", BindingFlags.NonPublic | BindingFlags.Instance);
        XmlWriter innerWriter = (XmlWriter)innerWriterPropInfo.GetValue(xwriter, null);
        FieldInfo encodingFieldInfo = innerWriter.GetType().GetField("encoding", BindingFlags.NonPublic | BindingFlags.Instance);
        encodingFieldInfo.SetValue(innerWriter, Encoding.UTF8);
        xwriter.WriteStartDocument(false);
        xwriter.WriteStartElement("EDXLDistribution", EDXLConstants.EDXLDE10Namespace);
        xwriter.WriteElementString("distributionID", this.distributionID);
        xwriter.WriteElementString("senderID", this.senderID);
        xwriter.WriteElementString("dateTimeSent", this.datetimeSent.ToLocalTime().ToString("yyyy-MM-ddTHH:mm:sszzz"));
        xwriter.WriteElementString("distributionStatus", this.distributionStatus.ToString());
        xwriter.WriteElementString("distributionType", this.distributionType.ToString());
        xwriter.WriteElementString("combinedConfidentiality", this.combinedConfidentiality);
        if (!String.IsNullOrEmpty(this.language))
        {
          xwriter.WriteElementString("language", this.language);
        }

        this.senderRole.WriteXML(xwriter);
        this.recipientRole.WriteXML(xwriter);
        this.keyword.WriteXML(xwriter);
        if (this.distributionReference.Count != 0)
        {
          foreach (string distribution in this.distributionReference)
          {
            if (String.IsNullOrEmpty(distribution))
            {
              continue;
            }

            this.CheckDistributionReference(distribution);
            xwriter.WriteElementString("distributionReference", distribution);
          }
        }

        if (this.explicitAddress.Count != 0)
        {
          foreach (ValueScheme scheme in this.explicitAddress)
          {
            xwriter.WriteStartElement("explicitAddress");
            scheme.WriteXML(xwriter);
            xwriter.WriteEndElement();
          }
        }

        if (this.targetArea.Count != 0)
        {
          foreach (TargetAreaType target in this.targetArea)
          {
            xwriter.WriteStartElement("targetArea");
            target.WriteXML(xwriter);
            xwriter.WriteEndElement();
          }
        }

        if (this.contentObjects.Count != 0)
        {
          foreach (ContentObject obj in this.contentObjects)
          {
            xwriter.WriteStartElement("contentObject");
            obj.WriteXML(xwriter);
            xwriter.WriteEndElement();
          }
        }

        xwriter.WriteEndElement();
        xwriter.Flush();
      }

      string strout = this.StripUnprintableCharacters(output.ToString());

      if (EDXLConstants.SecondarySchemaValidation)
      {
        this.ValidateToSchema(strout);
      }

      return strout;
    }

    /// <summary>
    /// Writes This DE Object to a Stream 
    /// </summary>
    /// <param name="output">Stream to Write UTF8 Formatted XML Data To</param>
    public void WriteXML(Stream output)
    {
      this.Validate();
      XmlWriterSettings xsettings = new XmlWriterSettings();
      xsettings.Encoding = Encoding.UTF8;
      xsettings.Indent = true;
      xsettings.IndentChars = "\t";
      
      MemoryStream ms = new MemoryStream();
      StringBuilder outputStringBuilder = new StringBuilder();
      using (XmlWriter xwriter = XmlWriter.Create(outputStringBuilder, xsettings))
      {
        PropertyInfo innerWriterPropInfo = xwriter.GetType().GetProperty("InnerWriter", BindingFlags.NonPublic | BindingFlags.Instance);
        XmlWriter innerWriter = (XmlWriter)innerWriterPropInfo.GetValue(xwriter, null);
        FieldInfo encodingFieldInfo = innerWriter.GetType().GetField("encoding", BindingFlags.NonPublic | BindingFlags.Instance);
        encodingFieldInfo.SetValue(innerWriter, Encoding.UTF8);
        xwriter.WriteStartDocument(false);
        xwriter.WriteStartElement("EDXLDistribution", EDXLConstants.EDXLDE10Namespace);
        xwriter.WriteElementString("distributionID", this.distributionID);
        xwriter.WriteElementString("senderID", this.senderID);
        xwriter.WriteElementString("dateTimeSent", this.datetimeSent.ToLocalTime().ToString("yyyy-MM-ddTHH:mm:sszzz"));
        xwriter.WriteElementString("distributionStatus", this.distributionStatus.ToString());
        xwriter.WriteElementString("distributionType", this.distributionType.ToString());
        xwriter.WriteElementString("combinedConfidentiality", this.combinedConfidentiality);
        if (!String.IsNullOrEmpty(this.language))
        {
          xwriter.WriteElementString("language", this.language);
        }

        this.senderRole.WriteXML(xwriter);
        this.recipientRole.WriteXML(xwriter);
        this.keyword.WriteXML(xwriter);

        if (this.distributionReference.Count != 0)
        {
          foreach (string distribution in this.distributionReference)
          {
            if (String.IsNullOrEmpty(distribution))
            {
              continue;
            }

            this.CheckDistributionReference(distribution);
            xwriter.WriteElementString("distributionReference", distribution);
          }
        }

        if (this.explicitAddress.Count != 0)
        {
          foreach (ValueScheme scheme in this.explicitAddress)
          {
            xwriter.WriteStartElement("explicitAddress");
            scheme.WriteXML(xwriter);
            xwriter.WriteEndElement();
          }
        }

        if (this.targetArea.Count != 0)
        {
          foreach (TargetAreaType target in this.targetArea)
          {
            xwriter.WriteStartElement("targetArea");
            target.WriteXML(xwriter);
            xwriter.WriteEndElement();
          }
        }

        if (this.contentObjects.Count != 0)
        {
          foreach (ContentObject obj in this.contentObjects)
          {
            xwriter.WriteStartElement("contentObject");
            obj.WriteXML(xwriter);
            xwriter.WriteEndElement();
          }
        }

        xwriter.WriteEndElement();
        xwriter.Flush();
      }

      byte[] a = System.Text.Encoding.UTF8.GetBytes(this.StripUnprintableCharacters(outputStringBuilder.ToString()));
      ms.Write(a, 0, a.Length);
      output.Write(a, 0, a.Length);

      if (EDXLConstants.SecondarySchemaValidation)
      {
        long pos = ms.Position;
        ms.Position = 0;

        StreamReader reader = new StreamReader(ms);

        string strout = reader.ReadToEnd();

        this.ValidateToSchema(strout);
        ms.Position = pos;
      }

      //ms.WriteTo(output);
      ms.Flush();
      ms.Close();
    }

    /// <summary>
    /// Reads This Object From an XML String
    /// </summary>
    /// <param name="xmlData">String of XML Data</param>
    public void ReadXML(string xmlData)
    {
      if (String.IsNullOrEmpty(xmlData))
      {
        throw new ArgumentNullException("XMLData Can't Be Null!");
      }

      XmlDocument doc = new XmlDocument();
      try
      {
        doc.LoadXml(xmlData);
      }
      catch (XmlException)
      {
        throw new ArgumentException("Invalid XML String");
      }

      XmlNodeList denodelist = doc.GetElementsByTagName("EDXLDistribution", EDXLConstants.EDXLDE10Namespace);
      if (denodelist.Count == 0)
      {
        throw new FormatException("No EDXLDistribution Element found!");
      }
      else if (denodelist.Count > 1)
      {
        throw new FormatException("Multiple EDXLDistribution Elements found!");
      }

      XmlNode deroot = denodelist[0];
      ValueScheme schemetmp;
      TargetAreaType targettmp;
      ContentObject contenttmp;

      this.senderRole.ReadXML(deroot);
      this.recipientRole.ReadXML(deroot);
      this.keyword.ReadXML(deroot);

      foreach (XmlNode node in deroot.ChildNodes)
      {
        if (String.IsNullOrEmpty(node.InnerText))
        {
          continue;
        }

        switch (node.LocalName)
        {
          case "distributionID":
            this.distributionID = node.InnerText;
            break;
          case "senderID":
            this.CheckSenderID(node.InnerText);
            this.senderID = node.InnerText;
            break;
          case "dateTimeSent":
            this.datetimeSent = DateTime.Parse(node.InnerText);
            if (this.datetimeSent.Kind == DateTimeKind.Unspecified)
            {
              this.datetimeSent = DateTime.MinValue;
              throw new ArgumentException("TimeZone Information Must Be Specified");
            }

            this.datetimeSent = this.datetimeSent.ToUniversalTime();
            break;
          case "distributionStatus":
            this.distributionStatus = (StatusValue)Enum.Parse(typeof(StatusValue), node.InnerText);
            break;
          case "distributionType":
            this.distributionType = (TypeValue)Enum.Parse(typeof(TypeValue), node.InnerText);
            break;
          case "combinedConfidentiality":
            this.combinedConfidentiality = node.InnerText;
            break;
          case "language":
            this.CheckRFC3066(node.InnerText);
            this.language = node.InnerText;
            break;
          case "senderRole":
            break;
          case "recipientRole":
            break;
          case "keyword":
            break;
          case "distributionReference":
            this.CheckDistributionReference(node.InnerText);
            this.distributionReference.Add(node.InnerText);
            break;
          case "explicitAddress":
            schemetmp = new ValueScheme();
            schemetmp.ReadXML(node);
            this.explicitAddress.Add(schemetmp);
            break;
          case "targetArea":
            targettmp = new TargetAreaType();
            targettmp.ReadXML(node);
            this.targetArea.Add(targettmp);
            break;
          case "contentObject":
            contenttmp = new ContentObject();
            contenttmp.ReadXML(node);
            this.contentObjects.Add(contenttmp);
            break;
          case "#comment":
            break;
          default:
            throw new ArgumentException("Unexpected Node Name: " + node.Name + " in EDXLDE");
        }
      }

      this.Validate();
      if (EDXLConstants.SecondarySchemaValidation)
      {
        this.ValidateToSchema(xmlData);
      }
    }

    /// <summary>
    /// Converts This IComposableMessage Into A Geo-RSS SyndicationItem
    /// </summary>
    /// <param name="items">Pointer to a Valid Syndication Item to Populate</param>
    public void ToGeoRSS(List<SyndicationItem> items)
    {
      if (items == null)
      {
        throw new ArgumentNullException("MyItems Can't Be Null");
      }

      SyndicationItem myitem;
      if (this.targetArea.Count == 0)
      {
        myitem = new SyndicationItem();
        myitem.Categories.Add(new SyndicationCategory(EDXLConstants.DESyndicationCategoryName, EDXLConstants.SyndicationCategoryScheme, EDXLConstants.SyndicationCategoryLabel));
        myitem.Copyright = new TextSyndicationContent(EDXLConstants.SyndicationCopyright);
        myitem.LastUpdatedTime = this.datetimeSent;
        myitem.Links.Add(new SyndicationLink(new Uri(EDXLConstants.SyndicationLinkBaseURI + "DE/")));
        myitem.PublishDate = DateTime.Now;
        myitem.Summary = new TextSyndicationContent("MessageID: " + this.distributionID);
        myitem.Title = new TextSyndicationContent(EDXLConstants.DESyndicationTitle);
        items.Add(myitem);
      }
      else
      {
        foreach (TargetAreaType mytargetarea in this.targetArea)
        {
          myitem = new SyndicationItem();
          myitem.Categories.Add(new SyndicationCategory(EDXLConstants.DESyndicationCategoryName, EDXLConstants.SyndicationCategoryScheme, EDXLConstants.SyndicationCategoryLabel));

          // Have to do this funky workaround due to the wacky structure of area here...
          StringBuilder output = new StringBuilder();
          XmlWriterSettings xsettings = new XmlWriterSettings();
          xsettings.Indent = true;
          xsettings.IndentChars = "\t";
          xsettings.OmitXmlDeclaration = true;
          XmlWriter xwriter = XmlWriter.Create(output, xsettings);
          xwriter.WriteStartElement(EDXLConstants.GeoRSSPrefix, "where", EDXLConstants.GeoRSSNamespace);
          mytargetarea.ToGeoRSS(xwriter);
          xwriter.WriteEndElement();
          xwriter.Flush();
          xwriter.Close();
          MemoryStream ms = new MemoryStream(Encoding.ASCII.GetBytes(output.ToString()));
          myitem.ElementExtensions.Add(XmlReader.Create(ms));
          myitem.Copyright = new TextSyndicationContent(EDXLConstants.SyndicationCopyright);
          myitem.LastUpdatedTime = this.datetimeSent;
          myitem.Links.Add(new SyndicationLink(new Uri(EDXLConstants.SyndicationLinkBaseURI + "DE/")));
          myitem.PublishDate = DateTime.Now;
          myitem.Summary = new TextSyndicationContent("MessageID: " + this.distributionID);
          myitem.Title = new TextSyndicationContent(EDXLConstants.DESyndicationTitle);
          items.Add(myitem);
        }
      }
    }

    /// <summary>
    /// Gets All Validation Errors in this DE Message
    /// </summary>
    /// <returns>null if no errors or list of string val for each error</returns>
    public List<string> GetDEErrors()
    {
      List<string> errors = new List<string>();
      if (this.distributionStatus == null)
      {
        errors.Add("Status Value Must be set in EDXLDE");
      }

      if (this.distributionType == null)
      {
        errors.Add("Type Value Must be set in EDXLDE");
      }

      if (this.distributionID == null)
      {
        errors.Add("Distribution ID is required and can't be null");
      }

      if (this.senderID == null)
      {
        errors.Add("Sender ID is required and can't be null");
      }

      if (this.datetimeSent == null || this.datetimeSent == DateTime.MinValue)
      {
        errors.Add("Date Time Sent is required and can't be null");
      }

      if (errors.Count > 0)
      {
        return errors;
      }
      else
      {
        return null;
      }
    }

    #endregion

    #region Protected Member Functions

    /// <summary>
    /// Remove some "unprintable" characters from the message
    /// </summary>
    /// <param name="str">String of XML Data</param>
    /// <returns>Sanitized String of Printable Characters</returns>
    protected string StripUnprintableCharacters(string str)
    {
      str = str.Replace("�", " "); // U+FFFD
      str = str.Replace("￼", " "); // U+FFFC
      return str;
    }

    /// <summary>
    /// Validates This Message Element For Required Values and Conformance
    /// </summary>
    protected void Validate()
    {
      if (this.distributionStatus == null)
      {
        throw new ArgumentNullException("Status Value Must be set in EDXLDE");
      }

      if (this.distributionType == null)
      {
        throw new ArgumentNullException("Type Value Must be set in EDXLDE");
      }

      if (String.IsNullOrEmpty(this.distributionID))
      {
        throw new ArgumentNullException("Distribution ID is required and can't be null");
      }

      if (String.IsNullOrEmpty(this.senderID))
      {
        throw new ArgumentNullException("Sender ID is required and can't be null");
      }

      if (this.datetimeSent == null || this.datetimeSent == DateTime.MinValue)
      {
        throw new ArgumentNullException("Date Time Sent is required and can't be null");
      }

      List<string> conformanceErrors;
      if ((conformanceErrors = this.CheckConformance()) != null)
      {
        throw new ArgumentException(conformanceErrors[0]);
      }
    }


    /// <summary>
    /// Checks This DE Object For Conformance to the DE Standard
    /// </summary>
    /// <returns>A List of Strings Containing Error Messages</returns>
    private List<string> CheckConformance()
    {
      List<string> conformanceErrors = new List<string>();
      if (conformanceErrors.Count > 0)
      {
        return conformanceErrors;
      }
      else
      {
        return null;
      }
    }

    /// <summary>
    /// Checks SenderID to the Conformance of string@string.string
    /// </summary>
    /// <param name="s">Sender ID String</param>
    private void CheckSenderID(string s)
    {
      if (String.IsNullOrEmpty(s))
      {
        throw new ArgumentNullException("Input string can't be null");
      }

      try
      {
        MailAddress temp = new MailAddress(s);
      }
      catch (Exception e)
      {
        throw new FormatException("SenderID must be in the form actor@domain\n" + e.ToString());
      }
    }

    /// <summary>
    /// Checks Input String For Compliance to RFC3066
    /// </summary>
    /// <param name="s">String in the form xx-xx</param>
    private void CheckRFC3066(string s)
    {
      if (String.IsNullOrEmpty(s))
      {
        throw new ArgumentException("Language string can't be null");
      }

      if (s.Length > 5 || (s.IndexOf('-') == -1 && s.Length > 2))
      {
        throw new ArgumentException("Language String Too Long.  Must be in the form RFC3066: xx-xx\n" + s);
      }

      if (s.Contains("-") && s.Length == 5)
      {
        return;
      }
      else if (s.Length == 2)
      {
        return;
      }
      else
      {
        throw new ArgumentException("Language String Incorrect.  Must be in the form RFC3066: xx-xx\n" + s);
      }
    }

    /// <summary>
    /// Checks Input String For Compliance to distid,senderid,datetimesent
    /// </summary>
    /// <param name="s">Input String in the form string,string@string.string,datetimestringwithtimezone</param>
    private void CheckDistributionReference(string s)
    {
      char[] seperator = new char[1] { ',' };
      List<string> split = s.Split(seperator).ToList();
      if (split.Count != 3)
      {
        throw new ArgumentException("Distribution Reference String Incorrect.  Must be in the form <distID>,<senderID>,<dateTimeSent>\n" + s);
      }

      this.CheckSenderID(split[1]);
      if (DateTime.Parse(split[2]).Kind == DateTimeKind.Unspecified)
      {
        throw new ArgumentException("Distribution Reference String Incorrect.  Timezone value must be specified");
      }
    }

    /// <summary>
    /// Validates The Current DE Object Against The XSD Schema File
    /// </summary>
    /// <param name="xmldata">XML String To Parse and Validate</param>
    private void ValidateToSchema(string xmldata)
    {
      XmlReader vr;
      XmlReaderSettings xs = new XmlReaderSettings();
      XmlSchemaSet coll = new XmlSchemaSet();
      StringReader xsdsr = new StringReader(EDXLSharp.EDXLDELib.Properties.Resources.EDXL_DE_Schema_v1_0);
      StringReader xmlsr = new StringReader(xmldata);
      XmlReader xsdread = XmlReader.Create(xsdsr);
      coll.Add(EDXLConstants.EDXLDE10Namespace, xsdread);
      xs.Schemas.Add(coll);
      xs.ValidationType = ValidationType.Schema;
      xs.ValidationFlags |= XmlSchemaValidationFlags.ProcessInlineSchema;
      xs.ValidationFlags |= XmlSchemaValidationFlags.ReportValidationWarnings;
      xs.ValidationEventHandler += new ValidationEventHandler((object sender, ValidationEventArgs args) =>
      {
          if (args.Severity == XmlSeverityType.Error)
          {
              schemaErrorString = schemaErrorString + args.Message + "\r\n";
              schemaErrors++;
          }
      });

      vr = XmlReader.Create(xmlsr, xs);
      while (vr.Read())
      {
      }

      vr.Close();
      xmlsr.Close();
      xsdread.Close();
      xsdsr.Close();
      if (schemaErrors > 0)
      {
        throw new ArgumentException("Schema Validation Error: " + schemaErrorString);
      }
    }

    /// <summary>
    /// Handler Function For Distribution Reference List Changed Event
    /// </summary>
    /// <param name="sender">Object That Is Firing The Event</param>
    /// <param name="e">Event Arguments for the List Changed Event</param>
    private void DistributionRefHandler(object sender, ListChangedEventArgs e)
    {
      if (e.ListChangedType == ListChangedType.ItemAdded || e.ListChangedType == ListChangedType.ItemChanged)
      {
        this.CheckDistributionReference(this.distributionReference[e.NewIndex]);
      }
    }
    #endregion
  }
}
